feat: Expand eye filter into multi-choice visibility filters - #254
Conversation
- Add DrinkVisibilityFilter enum (availableOnly, notTasted, veganOnly, allergenFree) - Add isVegan field to Product (parses is_vegan from JSON) - Add isAllergenFree computed property to Product/Drink - Update DrinkFilterService: new filterByNotTasted/Vegan/AllergenFree methods, filterDrinks now takes Set<DrinkVisibilityFilter> instead of bool hideUnavailable - Update BeerProvider: replace _hideUnavailable with _visibilityFilters set, add setVisibilityFilter, keep setHideUnavailable/hideUnavailable for compat, persist all filters under visibilityFilters prefs key, migrate legacy key - Replace _AvailabilityButton toggle with _VisibilityFilterButton that opens _VisibilityFilterSheet with 4 checkboxes and active-filter badge - Add tests: isVegan/isAllergenFree model tests, new filter service tests, new beer provider tests for all 4 visibility filters Agent-Logs-Url: https://github.com/richardthe3rd/cambridge-beer-festival-app/sessions/2e71429f-96a9-46bb-94cc-6f940fce1975 Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com>
…t legacy pref key not written Agent-Logs-Url: https://github.com/richardthe3rd/cambridge-beer-festival-app/sessions/2e71429f-96a9-46bb-94cc-6f940fce1975 Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com>
…ar-all efficiency - Add missing `});` closing the visibility filters test group (build was broken) - Remove unused `provider` field from `_VisibilityFilterSheet` (Consumer handles rebuilds) - Add `clearVisibilityFilters()` to BeerProvider for a single atomic prefs write - Update Clear button to call `clearVisibilityFilters()` instead of N sequential writes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `dense: true` and a `Semantics` wrapper (label/value/selected/button) to `_VisibilityFilterTile` to match the pattern used by `_StyleFilterSheet`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep isVegan field name from this PR but preserve the robust multi-type parsing (bool/num/string) introduced by #252. Remove the duplicate fragile cast and rename all .vegan accessor references to .isVegan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
c96f8a3 to
1be2eb1
Compare
Replace the single 'allergen-free' checkbox with a dynamic list of per- allergen filters derived from the loaded drinks data. Each allergen (e.g. gluten, sulphites) gets its own checkbox; a drink passes if the allergen value is 0 or absent. Multiple selections are ANDed. - Remove allergenFree from DrinkVisibilityFilter enum - Add filterByExcludedAllergens() to DrinkFilterService - Add excludedAllergens param to filterDrinks() - Add excludedAllergens, availableAllergens, setAllergenFilter(), clearAllergenFilters() to BeerProvider with prefs persistence - Sheet shows a labelled allergen section with sorted per-allergen tiles - Badge count includes both visibility and allergen filter counts - Clear button clears both visibility and allergen filters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`json['allergens'] as Map<String, dynamic>?` throws at runtime when the
value is a `Map<dynamic, dynamic>` literal (e.g. `{}` in tests, or from
a JSON decoder that hasn't been typed). Switch to an `is Map` guard which
accepts any map type and still lets the per-entry parsing handle values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LCOV of commit
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://copilot-update-eye-filter-op.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
…nk.isAllergenFree Adds three tests to close the coverage gaps flagged by Codecov on this PR: - setAllergenFilter with active=false (remove branch) - clearVisibilityFilters() method - Drink.isAllergenFree delegate getter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://copilot-update-eye-filter-op.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
There was a problem hiding this comment.
Pull request overview
This PR expands the drinks list “eye” toggle into a multi-choice visibility/dietary filtering system, enabling independent filters (availability, not tasted, vegan-only, and per-allergen exclusions) with persistence in SharedPreferences.
Changes:
- Add vegan/allergen-related model fields (
Product.isVegan,Product.isAllergenFree) and propagate them throughDrink. - Introduce
DrinkVisibilityFilterand extendDrinkFilterService+BeerProviderto support multiple visibility filters and excluded allergens (including persistence and legacy handling). - Replace the single availability button with a filter button + bottom sheet UI, and update/extend test coverage.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
lib/models/drink.dart |
Replaces vegan with isVegan, adds isAllergenFree, and updates JSON parsing/serialization. |
lib/domain/models/models.dart |
Exports the new visibility-filter enum. |
lib/domain/models/drink_visibility_filter.dart |
Adds DrinkVisibilityFilter enum for multi-toggle visibility filters. |
lib/domain/services/drink_filter_service.dart |
Updates filtering API to accept visibilityFilters + excludedAllergens and adds new filter helpers. |
lib/providers/beer_provider.dart |
Replaces _hideUnavailable with _visibilityFilters, adds _excludedAllergens and availableAllergens, and persists filter state. |
lib/screens/drinks_screen.dart |
Replaces the availability toggle with a filter button (badge) and adds a visibility/allergen filter bottom sheet. |
lib/screens/drink_detail_screen.dart |
Updates vegan indicator to use drink.isVegan. |
test/models_test.dart |
Updates existing vegan assertions and adds coverage for isVegan parsing + isAllergenFree. |
test/domain/services/drink_filter_service_test.dart |
Adds coverage for not-tasted/vegan/allergen-exclusion filtering and updates filterDrinks calls. |
test/beer_provider_test.dart |
Adds coverage for new visibility/allergen filter persistence and legacy preference behavior. |
test/drink_detail_screen_test.dart |
Updates test product construction to use isVegan. |
| Set<DrinkVisibilityFilter> visibilityFilters = const {}, | ||
| Set<String> excludedAllergens = const {}, |
| // Load visibility filter preferences (with migration from legacy hideUnavailable key) | ||
| _visibilityFilters = {}; | ||
| final savedFilters = prefs.getStringList('visibilityFilters'); | ||
| if (savedFilters != null) { | ||
| for (final name in savedFilters) { | ||
| final filter = DrinkVisibilityFilter.values | ||
| .where((f) => f.name == name) | ||
| .firstOrNull; | ||
| if (filter != null) _visibilityFilters.add(filter); | ||
| } | ||
| } else { | ||
| // Migrate from legacy 'hideUnavailable' boolean preference | ||
| if (prefs.getBool('hideUnavailable') ?? false) { | ||
| _visibilityFilters.add(DrinkVisibilityFilter.availableOnly); | ||
| } | ||
| } |
| void _showVisibilityFilter(BuildContext context, BeerProvider provider) { | ||
| showModalBottomSheet( | ||
| context: context, | ||
| isScrollControlled: true, | ||
| builder: (context) => const _VisibilityFilterSheet(), | ||
| ); | ||
| } |
| child: Container( | ||
| width: 14, | ||
| height: 14, | ||
| decoration: BoxDecoration( | ||
| color: theme.colorScheme.primary, | ||
| shape: BoxShape.circle, | ||
| ), | ||
| child: Center( | ||
| child: Text( | ||
| '$activeCount', | ||
| style: TextStyle( | ||
| color: theme.colorScheme.onPrimary, | ||
| fontSize: 9, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| ), |
| Set<String> get availableAllergens { | ||
| final allergens = <String>{}; | ||
| for (final drink in _allDrinks) { | ||
| allergens.addAll(drink.allergens.keys); |
The single availability toggle ("eye" button) is replaced with a multi-choice filter sheet, giving users independent control over visibility and dietary conditions.
New filters
is_vegan: true0or absent; multiple selections are ANDedDomain / model changes
Product.isVegan— nullable bool, parsed robustly fromis_vegan/veganJSON fields (handlesbool,num, andStringvalues)Product.isAllergenFree— computed:allergensempty or all values0DrinkVisibilityFilterenum added tolib/domain/models/(availableOnly,notTasted,veganOnly)DrinkFilterService.filterDrinkssignature changed:bool hideUnavailable→Set<DrinkVisibilityFilter> visibilityFilters+Set<String> excludedAllergensfilterByNotTasted,filterByVegan,filterByExcludedAllergensmethods added toDrinkFilterServiceProvider
_hideUnavailable: boolreplaced with_visibilityFilters: Set<DrinkVisibilityFilter>hideUnavailablegetter preserved asvisibilityFilters.contains(availableOnly)for backward compatsetVisibilityFilter(filter, active)andclearVisibilityFilters()added;setHideUnavailablebecomes a thin wrapper_excludedAllergens: Set<String>added withsetAllergenFilter(allergen, active)andclearAllergenFilters()availableAllergensgetter — derived live from loaded drinks, drives the allergen section of the sheetvisibilityFilters(string list),excludedAllergens(string list); migrates legacyhideUnavailablebool on first runUI
_AvailabilityButton(toggle) →_VisibilityFilterButton: eye icon with a numeric badge (visibilityFilters.length + excludedAllergens.length) when any filter is active, opens_VisibilityFilterSheetCheckboxListTilerows (available only, not tasted, vegan only), then a labelled "Allergen-free" section with alphabetically-sorted per-allergen rows drawn fromavailableAllergensTests
New/updated coverage for:
isVeganmodel parsing (true/false/null/numeric/string variants)isAllergenFreecomputed propertyfilterByNotTasted,filterByVegan,filterByExcludedAllergensservice methods (including value-0 and missing-key pass-through cases, AND logic for multiple exclusions)BeerProviderincluding persistence, legacy key migration, and allergen filter persistence/restore